package org.zhaya.sdk.processors;

import io.logz.sawmill.Doc;
import io.logz.sawmill.ProcessResult;
import io.logz.sawmill.Processor;
import io.logz.sawmill.annotations.ProcessorProvider;
import io.logz.sawmill.exceptions.ProcessorConfigurationException;
import io.logz.sawmill.utilities.JsonUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static java.util.Objects.requireNonNull;

@Slf4j
@ProcessorProvider(type = "anonymize", factory = AnonymizeProcessor.Factory.class)
public class AnonymizeProcessor implements Processor {
    private final List<String> fields;
    private final Algorithm algorithm;
    private final Integer subIndex;

    public AnonymizeProcessor(List<String> fields, Algorithm algorithm, Integer subIndex) {
        this.fields = requireNonNull(fields, "fields cannot be null");
        this.algorithm = algorithm;
        this.subIndex = subIndex;
    }

    @Override
    public ProcessResult process(Doc doc) {
        List<String> missingFields = new ArrayList<>();
        for (String field : fields) {
            if (!doc.hasField(field, String.class)) {
                missingFields.add(field);
                continue;
            }

            String value = doc.getField(field);
            doc.addField(field, algorithm.anonimize(value, subIndex));
        }

        if (!missingFields.isEmpty()) {
            return ProcessResult.failure(String.format("failed to anonymize fields in path [%s], fields are missing or not instance of [String]", missingFields));
        }

        return ProcessResult.success();
    }

    public static class Factory implements Processor.Factory {
        public Factory() {
        }

        @Override
        public AnonymizeProcessor create(Map<String, Object> config) {
            Configuration anonymizeConfig = JsonUtils.fromJsonMap(Configuration.class, config);

            if (CollectionUtils.isEmpty(anonymizeConfig.getFields())) {
                throw new ProcessorConfigurationException("failed to parse anonymize config, cannot be processed without fields");
            }

            if (StringUtils.isEmpty(anonymizeConfig.getKey())) {
                throw new ProcessorConfigurationException("failed to parse anonymize config, cannot be processed without key");
            }

            return new AnonymizeProcessor(anonymizeConfig.getFields(),
                    anonymizeConfig.getAlgorithm(),
                    anonymizeConfig.getSubIndex());
        }
    }

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Configuration implements Processor.Configuration {
        private List<String> fields;
        private Algorithm algorithm = Algorithm.SHA1;
        private String key;
        private Integer subIndex;

    }

    public enum Algorithm {
        SHA1 {
            @Override
            public String anonimize(String value, Integer subIndex) {
                return subString(DigestUtils.sha1Hex(value), subIndex);
            }
        },
        SHA256 {
            @Override
            public String anonimize(String value, Integer subIndex) {
                return subString(DigestUtils.sha256Hex(value), subIndex);
            }
        },
        SHA384 {
            @Override
            public String anonimize(String value, Integer subIndex) {
                return subString(DigestUtils.sha384Hex(value), subIndex);
            }
        },
        SHA512 {
            @Override
            public String anonimize(String value, Integer subIndex) {
                return subString(DigestUtils.sha512Hex(value), subIndex);
            }
        },
        MD5 {
            @Override
            public String anonimize(String value, Integer subIndex) {
                return subString(DigestUtils.md5Hex(value), subIndex);
            }
        },
        ExchangeOddEven {
            @Override
            public String anonimize(String value, Integer subIndex) {
                return subString(exchangeOddEven(value), subIndex);
            }
        },
        SM3 {
            @Override
            public String anonimize(String value, Integer subIndex) {
                return subString(encrypt(value), subIndex);
            }
        };

        public abstract String anonimize(String value, Integer subIndex);

        public String subString(String s, Integer subIndex) {
            if (null != subIndex && null != s && subIndex < s.length()) {
                return s.substring(subIndex);
            }
            return s;
        }

        public String exchangeOddEven(String str) {
            if (StringUtils.isNotBlank(str)) {
                char[] a = str.toCharArray();
                for (int j = 0; j < str.length() - 1; j = j + 2) {
                    char temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                }
                return String.valueOf(a);
            }
            return str;
        }

        public static String encrypt(String value) {
            byte[] srcData = value.getBytes(StandardCharsets.UTF_8);
            SM3Digest digest = new SM3Digest();
            digest.update(srcData, 0, srcData.length);
            byte[] hash = new byte[digest.getDigestSize()];
            digest.doFinal(hash, 0);
            String result = ByteUtils.toHexString(hash);
            if (StringUtils.isNotBlank(result)) {
                result = result.toUpperCase();
            }
            return result;
        }
    }
}